#include "herm/base/utils/misc.h"
#include "socket_os.h"
#include "socket_def.h"
#include "socket_base.h"

namespace herm{

void Init()
{  
#ifdef WIN32
    WSADATA wsaData;
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
		throw HERM_SOCKET_EXCEPTION(ST_OTHER);

    if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) 
		throw HERM_SOCKET_EXCEPTION(ST_OTHER);
#endif
}

void UnInit()
{    
#ifdef WIN32
	WSACleanup();
#endif
}

namespace Private{

int StreamType::Family()
{
	return AF_INET;
}

int StreamType::SocketType()
{
	return SOCK_STREAM;
}

int StreamType::Protocol()
{
	return IPPROTO_TCP;
}

int DatagramType::Family()
{
	return AF_INET;
}

int DatagramType::SocketType()
{
	return SOCK_DGRAM;
}

int DatagramType::Protocol()
{
	return IPPROTO_UDP;
}
}

SocketBase::SocketBase(void)
{
	m_block = true;
}

SocketBase::~SocketBase(void)
{
}

void SocketBase::SetBlock()
{
	HERM_ASSERT(m_socket != INVALID_HANDLE);

#ifdef WIN32
	u_long val = 0;
	if (ioctlsocket(m_socket, FIONBIO, &val) == Private::FAILED)
		throw HERM_SOCKET_EXCEPTION(ST_OTHER);
#else
	int opt = fcntl(m_socket, F_GETFL);
	if (opt == -1)
		throw HERM_SOCKET_EXCEPTION(ST_OTHER);
	
	if (fcntl(m_socket, F_SETFL, opt & ~O_NONBLOCK) == -1)
		throw HERM_SOCKET_EXCEPTION(ST_OTHER);
#endif

	m_block = true;
}

void SocketBase::SetNonblock()
{
	HERM_ASSERT(m_socket != INVALID_HANDLE);

#ifdef WIN32
	u_long val = 1;
	if (ioctlsocket(m_socket, FIONBIO, &val) == Private::FAILED)
		throw HERM_SOCKET_EXCEPTION(ST_OTHER);
#else
	int opt = fcntl(m_socket, F_GETFL);
	if (opt == -1)
		throw HERM_SOCKET_EXCEPTION(ST_OTHER);
	
	if (fcntl(m_socket, F_SETFL, opt | O_NONBLOCK) == -1)
		throw HERM_SOCKET_EXCEPTION(ST_OTHER);
#endif

	m_block = false;
}

int SocketBase::GetSendBufSize() const
{
	return GetSockOpt(SOL_SOCKET, SO_SNDBUF);
}
	
void SocketBase::SetSendBufSize(int size)
{
	SetSockOpt(SOL_SOCKET, SO_SNDBUF, size, sizeof(int));
}

int SocketBase::GetRecvBufSize() const
{
	return GetSockOpt(SOL_SOCKET, SO_RCVBUF);
}

void SocketBase::SetRecvBufSize(int size)
{
	SetSockOpt(SOL_SOCKET, SO_RCVBUF, size, sizeof(int));
}

bool SocketBase::GetReuseAddr() const
{
	return GetSockOpt(SOL_SOCKET, SO_REUSEADDR) == 1;
}

void SocketBase::SetReuseAddr(bool opt)
{
	int optVal = opt;
	int valLen = sizeof(int);
	SetSockOpt(SOL_SOCKET, SO_REUSEADDR, optVal, valLen);
}

bool SocketBase::GetLocalAddress(Address& addr, bool isDatagram) const
{
	(void)isDatagram;
	// On Win32, if the socket is using a connectionless protocol, the address may not be 
	// available until I/O occurs on the socket.
#ifdef WIN32
	if (isDatagram)
	{
		herm::Address tmpAddr(65534);
		int bytes = sendto(m_socket, "0", 0, 0, (sockaddr*)&tmpAddr.SockAddr(), sizeof(sockaddr));
		int code = WSAGetLastError();
	}
#endif

	sockaddr_in sin;
	memset(&sin, 0, sizeof(sockaddr_in));
	SockLen len = sizeof(sockaddr_in);
	if (getsockname(m_socket, (sockaddr*)&sin, &len) == Private::FAILED)
		throw HERM_SOCKET_EXCEPTION(ST_OTHER);

	addr.SockAddr(sin);

	std::string tmpIP = addr.IP();
	if (tmpIP != "0.0.0.0" && tmpIP != "127.0.0.1" && tmpIP.substr(0, 3) != "169")
		return true;

	char name[512];
	if (gethostname(name, 512) == -1)
		throw false;

	hostent* h = gethostbyname(name);
	if (!h)
		return false;

	bool ret = false;
	for (int i = 0; h->h_addr_list[i] != nullptr; i++)
	{
		in_addr inaddr = *(in_addr*)h->h_addr_list[i];
		tmpIP = inet_ntoa(inaddr);
		if (tmpIP == "0.0.0.0" || tmpIP == "127.0.0.1" || tmpIP.substr(0, 3) == "169")
			continue;

		sin.sin_addr = inaddr;
		ret = true;
		break;
	}

	if (!ret)
		return false;
	
	addr.SockAddr(sin);

	return true;
}

void SocketBase::GetRemoteAddress(Address& addr) const
{
	sockaddr_in sin;
	memset(&sin, 0, sizeof(sockaddr_in));
	SockLen len = sizeof(sockaddr_in);
	if (getpeername(m_socket, (sockaddr*)&sin, &len) == Private::FAILED)
		throw HERM_SOCKET_EXCEPTION(ST_OTHER);

	addr.SockAddr(sin);
}

int SocketBase::GetSockOpt(int level, int optName) const
{
	int optVal;
	SockLen valLen = sizeof(int);
	if (getsockopt(m_socket, level, optName, (char*)&optVal, &valLen) == Private::FAILED)
		throw HERM_SOCKET_EXCEPTION(ST_OTHER);

	return optVal;
}

void SocketBase::SetSockOpt(int level, int optName, int optVal, SockLen valLen)
{
	if (setsockopt(m_socket, level, optName, (const char*)&optVal, valLen) == Private::FAILED)
		throw HERM_SOCKET_EXCEPTION(ST_OTHER);
}

SocketException::SocketException(const std::string& file, int line, const std::string& func, int type, int code) 
	: Exception(file, line, func, type, code, Private::GetLastStrError(code))
{
}

SocketException::SocketException(const std::string& file, int line, const std::string& func, int type, const std::string& desc)
    : Exception(file, line, func, type, 0, desc)
{
}
}

